/*
 * @Author: sundeqi
 * @Date: 2022-04-15 16:19:39
 * @LastEditors: sundeqi
 * @LastEditTime: 2022-05-11 17:02:01
 * @Description: 自动补全
 */
//自动提示补全控件
import 'package:flutter/material.dart';

class SuggestionOverlay extends StatefulWidget {
  final TextEditingController controller;
  final List<String> suggestion;
  final Widget textField;
  final FocusNode focusNode;
  final Color? background;
  final TextStyle? textStyle;
  final VoidCallback? onEnd;
  final int? offsetY;

  const SuggestionOverlay(
      {Key? key,
      required this.textField,
      required this.focusNode,
      required this.suggestion,
      required this.controller,
      this.onEnd,
      this.offsetY,
      this.textStyle,
      this.background})
      : super(key: key);

  @override
  State<SuggestionOverlay> createState() => _SuggestionOverlayState();
}

class _SuggestionOverlayState extends State<SuggestionOverlay> {
  final LayerLink _layerLink = LayerLink(); //overlay跟随
  OverlayEntry? overlayEntry;
  List<String> _filteredSuggestion = [];

  Size? _textFieldSize;
  bool _inBuilding = true;
  bool _isKeyboardInput = true;

  @override
  void initState() {
    super.initState();
    widget.controller.addListener(onTextChanged);
    widget.focusNode.addListener(onFocusChange);
    WidgetsBinding.instance.addPostFrameCallback((_) {
      _inBuilding = false;
    });
  }

  void onTextChanged() {
    if (_inBuilding) return;
    _textFieldSize ??= (context.findRenderObject() as RenderBox).size;
    String inputString = widget.controller.text.trim();
    if (inputString.isEmpty || !focus) {
      return _hideOverlay();
    }
    _filteredSuggestion = getSuggestion(inputString);
    if (_filteredSuggestion.length == 1 && !_isKeyboardInput) {
      String text = _filteredSuggestion.first;
      if (inputString == text) return;
    }
    _isKeyboardInput = true;
    Future.delayed(Duration.zero, () {
      if (overlayEntry == null) {
        overlayEntry = _createOverly();
        Overlay.of(context)?.insert(overlayEntry!);
      }
      overlayEntry!.markNeedsBuild();
    });
  }

  bool get focus => widget.focusNode.hasFocus;

  void onFocusChange() {
    if (!focus) {
      _hideOverlay();
    }
  }

  void _hideOverlay() {
    overlayEntry?.remove();
    overlayEntry = null;
  }

  @override
  void didUpdateWidget(covariant SuggestionOverlay oldWidget) {
    if (oldWidget.controller != widget.controller) {
      oldWidget.controller.removeListener(onTextChanged);
      widget.controller.addListener(onTextChanged);
    }
    if (oldWidget.focusNode != widget.focusNode) {
      oldWidget.focusNode.removeListener(onFocusChange);
      widget.focusNode.addListener(onFocusChange);
    }

    _hideOverlay();
    super.didUpdateWidget(oldWidget);
  }

  @override
  void dispose() {
    overlayEntry?.remove();
    widget.focusNode.removeListener(onFocusChange);
    widget.controller.removeListener(onTextChanged);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return CompositedTransformTarget(link: _layerLink, child: widget.textField);
  }

  List<String> getSuggestion(String string) {
    String tag = "@";
    int index = string.indexOf(tag);
    if (index == -1) {
      return widget.suggestion.map((e) => string + tag + e).toList();
    }
    String substring = string.substring(index);
    if (substring.length == 1) {
      return widget.suggestion.map((e) => string + e).toList();
    } else if (substring.length > 1) {
      String tempStr = substring.substring(1);
      List<String> list = [];
      for (var item in widget.suggestion) {
        if (item.contains(tempStr)) {
          list.add(item);
        }
      }
      String preStr = string.substring(0, index);
      return list.map((e) => preStr + tag + e).toList();
    }
    return [];
  }

  double? textFieldHeight;
  double? textFieldWidth;

  /// 创建匹配选择悬浮框
  OverlayEntry _createOverly() {
    RenderBox renderBox = context.findRenderObject() as RenderBox;
    final Size textFieldSize = renderBox.size;
    textFieldWidth = textFieldWidth ?? textFieldSize.width;
    textFieldHeight = textFieldHeight ?? textFieldSize.height;
    final textFieldOffset = renderBox.localToGlobal(Offset.zero);
    final wHeight = MediaQuery.of(context).size.height;
    double offsetY = 0;
    if (_textFieldSize != null) {
      offsetY = (_textFieldSize!.height - textFieldHeight!).abs();
    }

    return OverlayEntry(builder: (_) {
      EdgeInsets viewInsets = MediaQuery.of(_).viewInsets;
      double bottom = viewInsets.bottom;
      final double maxHeight = wHeight -
          textFieldOffset.dy -
          bottom -
          textFieldHeight! -
          viewInsets.top;
      final double minHeight = maxHeight < 100 ? 100 : maxHeight;
      return Positioned(
        width: textFieldWidth,
        child: CompositedTransformFollower(
          offset: Offset(0, textFieldHeight! - (widget.offsetY ?? offsetY)),
          link: _layerLink,
          child: Container(
              constraints: BoxConstraints(maxHeight: minHeight),
              padding: const EdgeInsets.only(bottom: 20),
              child: Card(
                margin: EdgeInsets.zero,
                color: widget.background,
                child: ListView.builder(
                  shrinkWrap: true,
                  padding: EdgeInsets.zero,
                  itemCount: _filteredSuggestion.length,
                  itemBuilder: (context, index) {
                    String text = _filteredSuggestion[index];
                    return InkWell(
                      child: Padding(
                        padding: const EdgeInsets.all(10),
                        child: Text(text,
                            style: widget.textStyle ??
                                const TextStyle(
                                  color: Colors.black,
                                )),
                      ),
                      onTap: () {
                        _isKeyboardInput = false;
                        widget.focusNode.unfocus();
                        widget.controller.text = text;
                        _hideOverlay();
                        widget.onEnd?.call();
                      },
                    );
                  },
                ),
              )),
        ),
      );
    });
  }
}
